Skip to content

docs(spec): point ActionSchema.method's worked example at the shipped data door - #15098

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14093-action-method-example-path
Sep 3, 2026
Merged

docs(spec): point ActionSchema.method's worked example at the shipped data door#15098
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14093-action-method-example-path

Conversation

@claude

@claude claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #14093

Clause ②: no — path limb fires (packages/spec/src/**), content limb no: comment text only

What was wrong

ActionSchema.method's TSDoc carried the only worked example of a type: 'api' PATCH
against the data API, and it pointed at /api/v1/sys_api_key/{id} — a path the router
never mounts. The /data segment was missing. Nothing catches the difference at
authoring time: objectstack validate does not check target, type: 'api' has no
author-time route validation, so the action parses green, renders, is clickable, and
404s at the click.

The two readings this correction stands on, cited on the tree at 6665c5c9

1. The shipped data door composes to PATCH /api/v1/data/:object/:id.

packages/rest/src/rest-server.ts:7664, inside registerCrudEndpoints:

const dataPath = `${basePath}${crud.dataPrefix}`;

registered at packages/rest/src/rest-server.ts:7913-7914:

method: 'PATCH',
path: `${dataPath}/:object/:id`,

basePath is getApiBasePath() (rest-server.ts:3637-3640api.apiPath ?? api.basePath + '/' + api.version), and crud.dataPrefix defaults to /data at
packages/spec/src/api/rest-server.zod.ts:252:

dataPrefix: z.string().default('/data').describe('URL prefix for data endpoints'),

2. Under required scoping, only the scoped path is registered.

packages/rest/src/rest-server.ts:3717-3722, in registerRoutes():

if (enableProjectScoping) {
    const scopedBase = this.getScopedBasePath(basePath);
    if (projectResolution === 'required') {
        // Strict: only scoped routes
        registerForBase(scopedBase);

with getScopedBasePath at :3646-3647 returning `${basePath}/environments/:environmentId`. So a correctly spelled unscoped full path still 404s on such a host — which is why the example now names the premise instead of promising a host-independent path.

A third reading fixes the placeholder spelling: target's own docblock at
packages/spec/src/ui/action.zod.ts:992-997 documents ${param.X} and ${ctx.X}
interpolation and names ctx.recordId explicitly. A bare {recordId} is newTabUrl's
convention alone.

The change — one docblock, verbatim

Before:

  /**
   * HTTP method to use when `type: 'api'`. Defaults to `POST`. Use `PATCH` to
   * call data-API update endpoints (e.g. `/api/v1/sys_api_key/{id}` with
   * `bodyExtra: { revoked: true }`).
   */

After:

  /**
   * HTTP method to use when `type: 'api'`. Defaults to `POST`. Use `PATCH` to
   * call data-API update endpoints. The shipped data door is
   * `PATCH {apiBase}/data/:object/:id` — `getApiBasePath()` + `crud.dataPrefix`,
   * i.e. `/api/v1` + `/data` on a default host — so the endpoint for one
   * record is e.g. `/api/v1/data/sys_api_key/${ctx.recordId}` with
   * `bodyExtra: { revoked: true }`. Two things the path depends on: the
   * `/data` segment (omit it and the action renders, is clickable, and 404s
   * at the click), and the `${ctx.X}` / `${param.X}` interpolation
   * {@link target} documents — a bare `{recordId}` placeholder is
   * {@link newTabUrl}'s convention alone and is not substituted here.
   *
   * The full path is host-dependent: under `enableProjectScoping` with
   * `projectResolution: 'required'` only
   * `/api/v1/environments/:environmentId/data/:object/:id` is registered, so
   * an unscoped path 404s on such a host — write `target` against the base
   * the host actually mounts.
   *
   * To write a field on the CURRENT record, prefer the declarative
   * {@link operation} `'update'` with {@link patch}: the platform performs
   * the write, so there is no endpoint, method or id placeholder to spell at
   * all. The `type: 'api'` + `PATCH` form above remains the way to call an
   * explicit endpoint.
   */

sys_api_key is deliberately kept — the defect was the path STRUCTURE, not which object
the example picks. No .describe() text moves, no schema member moves, and no other
member of action.zod.ts is touched (the operation / patch block from PR #15077
included). packages/rest and packages/runtime are read-only inputs here.

Deviation from the dispatch: a patch changeset, not skip-changeset

The dispatch expected skip-changeset on the premise "comment text; no published
artifact moves". Measured on the built tree, that premise does not hold, so AGENTS.md
governs and this PR carries a @objectstack/spec patch changeset instead:

  • packages/spec/package.json files ships dist and src/**/*.zod.ts.
  • The corrected docblock is present in 13 .js and 13 .mjs bundles under
    packages/spec/dist/ after a build (e.g. dist/ui/index.mjs:8045-8072), and in the
    shipped src/ui/action.zod.ts itself.
  • So the diff publishes changed bytes from a released package (17.2.0). AGENTS.md's
    Post-Task Checklist §3 reserves that for a patch changeset and refuses
    skip-changeset for it by name: that label "is for a diff that publishes nothing from
    any released package."

Measured counterpoint, recorded because it is the surface most people assume: the
docblock reaches no .d.ts — 0 hits, and 0 for two control docblocks in the same
file as well, so this package emits no TSDoc into its declarations at all. IDE hover
text is unchanged; the npm tarball's bytes are not.

⚠️ This is a deviation from the dispatch text and is flagged rather than taken silently.
If the PM prefers the label, the changeset is one file to delete.

Verification record — all at final head b5bceb76

Every exit code captured before any pipe; verdict lines quoted from the gates themselves.

Step Result
pnpm --filter @objectstack/spec build exit 0 — check-dts-emitted: 34/34 declared declaration file(s) present
pnpm build (whole workspace) exit 0 — Tasks: 72 successful, 72 total
spec suite, vitest run --maxWorkers=2 exit 0 — Test Files 466 passed (466), Tests 12427 passed (12427)
pnpm --filter @objectstack/spec typecheck exit 0 — check:test-typecheck: OK; edited file proven in-program via tsc --noEmit --listFiles (1 hit)
check:generated exit 0 — ✓ All 15 generated artifacts are up to date (also green on the pre-edit baseline, so the TSDoc projects into no generated artifact and nothing needed --fix)
check:api-surface exit 0
check:docs exit 0
check-system-context-census.mjs exit 0 — OK — 106 elevation read sites in 20 packages across 45 files, all anchored (no --fix needed)
eslint, repo-wide eslint . --no-inline-config exit 0 — the full repo scan, not a narrowed one
control-byte self-scan over both edited files no hits
dispatch-gates.mjs --commands union 54/54 commands run; 53 exit 0, 1 NOT MEASURED

The union was re-derived at final head b5bceb76 and is byte-identical to the
derivation taken before the changeset landed — same 54 commands, same 2 paths.

The one NOT MEASURED gate, listed as such rather than as a pass:

  • node scripts/check-test-completeness.mjs — exit 3, structurally unrunnable
    locally. Its own text: "this gate grades a saved turbo run test log, and no log was
    named … the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there
    is nothing here to fix." CI tees the log and passes the path, so CI measures it.

Three further gates first returned a prerequisite code and were converted into real
measurements
by building rather than being written off:
check-dev-prereqs.mjs (exit 1 → 0 after pnpm build: ✓ 67 package build artifacts present),
check:doc-formula-expressions (exit 3 → 0 after building @objectstack/formula then @objectstack/lint),
and check:dual-build-cjs-loads (exit 3 → 0: 102 published require entry point(s) across 66 package(s) load).

Reverse verification is not owed for a comment-only change; the before/after docblock
above and the two cited route readings stand in its place.

Notes for the reviewer

Generated by Claude Code


Generated by Claude Code

`ActionSchema.method`'s TSDoc pointed its only worked example of a
`type: 'api'` PATCH at `/api/v1/sys_api_key/{id}`. The shipped data door
is `PATCH /api/v1/data/:object/:id` — `getApiBasePath()` composed with
`crud.dataPrefix` (default `/data`) — so an author copying the example
validated green and 404'd at the click, the same silent shape as an
unregistered handler arriving through a doc example.

The docblock now spells the path structure the router actually mounts,
keeps `sys_api_key` (the error was the structure, not the object), uses
`${ctx.recordId}` per `target`'s own interpolation convention, names the
scoping premise that makes any full path host-dependent, and points a
single-record field write at the declarative `operation: 'update'` +
`patch` form instead.

Comment text only: no schema member, no `.describe()` and no runtime
behaviour moves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
The dispatch expected `skip-changeset` on the premise that a comment-only
change publishes nothing. Measured on the built tree, that premise does not
hold: `@objectstack/spec`'s `files` array ships `dist/**` and
`src/**/*.zod.ts`, and the corrected docblock is present in 13 `.js` + 13
`.mjs` bundles and in the shipped source. So this diff does publish changed
bytes from a released package, which AGENTS.md's Post-Task Checklist reserves
for a `patch` changeset and explicitly refuses `skip-changeset` for.

It reaches no `.d.ts` — measured 0 hits there, and 0 for two control
docblocks too, so this package emits no TSDoc into its declarations at all.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions github-actions Bot added the size/s label Sep 3, 2026
@claude claude Bot changed the title docs(spec): point 's worked example at the shipped data door docs(spec): point ActionSchema.method's worked example at the shipped data door Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 8 documentable anchor(s).

20 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 0f94cc7cc1fa937940c71bdeaea1d62ae3ff113f.

3 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: /api/v1 (route, 82 pages)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 0f94cc7cc1fa937940c71bdeaea1d62ae3ff113fpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 1ce1506e99d0dfe92bcd6c774a8d2a73c41b41de — the merge of head b5bceb76907ccf2e7ad49a4d9880e109025c1524 into base 0f94cc7cc1fa937940c71bdeaea1d62ae3ff113f, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1ce1506e99d0dfe92bcd6c774a8d2a73c41b41de && git checkout 1ce1506e99d0dfe92bcd6c774a8d2a73c41b41de
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0f94cc7cc1fa937940c71bdeaea1d62ae3ff113f b5bceb76907ccf2e7ad49a4d9880e109025c1524 && git checkout -B drift-repro 0f94cc7cc1fa937940c71bdeaea1d62ae3ff113f && git merge --no-ff b5bceb76907ccf2e7ad49a4d9880e109025c1524

node scripts/docs-audit/affected-docs.mjs --json 0f94cc7cc1fa937940c71bdeaea1d62ae3ff113f

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 0f94cc7cc1fa937940c71bdeaea1d62ae3ff113f → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Copy link
Copy Markdown
Collaborator

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T22:40Z) — flipping to ready and enabling auto-merge (squash).


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/s tooling

Projects

None yet

2 participants